package net.piggy.common.paging.interceptor;

import net.piggy.common.constant.ParameterConstant;
import net.piggy.common.paging.page.Page;
import net.piggy.common.util.general.ReflectionUtil;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Properties;

/***
 * 分页
 */
@Intercepts({
        @Signature(method = "prepare", type = StatementHandler.class, args = {Connection.class, Integer.class})
})
public class MybatisPageInterceptor implements Interceptor {

    private final Logger logger = LoggerFactory.getLogger(MybatisPageInterceptor.class);
    private final static String delegate = "delegate";
    private final static String mappedStatement = "mappedStatement";
    private final static String sql = "sql";
    private Properties properties;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget();
        Field delegateField = ReflectionUtil.getField(handler, delegate);
        if(delegateField!=null){
            delegateField.setAccessible(true);
            StatementHandler statementHandler = (StatementHandler)delegateField.get(handler);
            BoundSql boundSql = statementHandler.getBoundSql();
            Object parameterObject = boundSql.getParameterObject();
            if (parameterObject instanceof Page<?>) {
                Page<?> page = (Page<?>) parameterObject;
                Field mappedStatementField = ReflectionUtil.getField(statementHandler, mappedStatement);
                if(mappedStatementField!=null){
                    if (page.isQueryTotal()) {
                        mappedStatementField.setAccessible(true);
                        MappedStatement mappedStatement = (MappedStatement)mappedStatementField.get(statementHandler);
                        Connection connection = (Connection) invocation.getArgs()[0];
                        this.setTotalCount(page, mappedStatement, connection);
                    }
                    if(page.isPaging()){
                        String pageSql = this.getPageSql(page, boundSql.getSql());
                        Field sqlField = boundSql.getClass().getDeclaredField(sql);
                        sqlField.setAccessible(true);
                        sqlField.set(boundSql, pageSql);
                    }
                }
                page.setTimestamp(System.currentTimeMillis());
            }
        }
        return invocation.proceed();
    }

    private void setTotalCount(Page<?> page, MappedStatement mappedStatement, Connection connection) {
        BoundSql boundSql = mappedStatement.getBoundSql(page);
        String sql = boundSql.getSql();
        String countSql = this.getCountSql(sql);
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        Configuration configuration = mappedStatement.getConfiguration();
        BoundSql countBoundSql = new BoundSql(configuration, countSql, parameterMappings, page);
        ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, page, countBoundSql);
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            preparedStatement = connection.prepareStatement(countSql);
            parameterHandler.setParameters(preparedStatement);
            resultSet = preparedStatement.executeQuery();
            if (resultSet.next()) {
                int totalCount = resultSet.getInt(1);
                page.setTotalCount(totalCount);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if(resultSet!=null){
                    resultSet.close();
                }
                if(preparedStatement!=null){
                    preparedStatement.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    private String getPageSql(Page<?> page, String sql) {
        String databaseType = properties.get("databaseType").toString();
        if(databaseType==null){
            databaseType = ParameterConstant.MYSQL;
        }
        StringBuilder stringBuilder = new StringBuilder(sql);

        int offset = (page.getPageNo() - 1) * page.getPageSize();
        switch (databaseType){
            case ParameterConstant.MYSQL:
                stringBuilder.append(" limit ").append(offset).append(", ").append(page.getPageSize());
                break;
            case ParameterConstant.ORACLE:
                stringBuilder.append(" offset ").append(offset).append(" rows fetch next ").append(page.getPageSize()).append(" rpw only");
                break;
            default:
                throw new RuntimeException("database type error ...");
        }
        return stringBuilder.toString();
    }

    private String getCountSql(String sql) {
        return "select count(1) from ("+sql+") piggy_count";
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        this.properties = properties;
    }
}
